package nobu.nakata.nfcfare.resource.builder;

import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nobu.nakata.nfcfare.exception.NFCBaseException;
import nobu.nakata.nfcfare.resource.item.ISuicaUsageResourceItem;
import nobu.nakata.nfcfare.resource.item.impl.SuicaUsageResourceItem;


public class SuicaUsageResouceItemBuilder {
	private static final Pattern PATTERN_USAGE		= Pattern.compile("^\\p{XDigit}{32}$");
	private static final int INDEX_MACHINE_TYPE		= 0;
	private static final int INDEX_USAGE_TYPE		= 1;
	private static final int INDEX_PAYMENT_METHOD	= 2;
	private static final int INDEX_SITUATION		= 3;
	private static final int INDEX_RECORDED_AT		= 4;
	private static final int INDEX_STATION			= 6;
	private static final int INDEX_REMINDER			= 10;
	private static final int INDEX_RESIDUE			= 12;
	private static final int INDEX_SERIAL			= 13;
	private static final int INDEX_REGION			= 15;
	private static final int _TERMINAL_				= 16;
	
	private static final int DATE_YEAR_SHIFT		= 9;
	private static final int DATE_YEAR_MASK			= 0xfe00;
	private static final int DATE_MONTH_SHIFT		= 5;
	private static final int DATE_MONTH_MASK		= 0x01e0;
	private static final int DATE_DAY_MASK			= 0x001f;
	
	private static final short[] TRAIN_USAGE_TYPES	= 
		new short[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x13};
	private static final short[] BUS_USAGE_TYPES	= 
		new short[]{0x0D, 0x0F, 0x1F};
	private static final short[] SHOP_USAGE_TYPES	= 
		new short[]{0x46, 0x4B, 0x49, 0xC6};
	
	
	private final short[] record = new short[16];
	
	private String trainBaseUrl;
	private String busBaseUrl;
	private Calendar calendar;
	
	
	public SuicaUsageResouceItemBuilder(String trainBaseUrl, String busBaseUrl, Calendar calendar) {
		this.trainBaseUrl	= trainBaseUrl;
		this.busBaseUrl		= busBaseUrl;
		this.calendar		= calendar;
	}

	
	public void fromUsageData(String usageData) throws SuicaUsageResouceItemBuilderException {
		Matcher matcher = PATTERN_USAGE.matcher(usageData);
		if (!matcher.matches())
			throw new SuicaUsageResouceItemBuilderException();
		for (int recordAt = 0; recordAt < _TERMINAL_; ++recordAt) {
			int charAt = recordAt * 2;
			record[recordAt] = Short.parseShort(usageData.substring(charAt, charAt + 2), 16);
		}
	}
	
	public ISuicaUsageResourceItem getItem() {
		List<String> stationUrls = new LinkedList<String>();
		ISuicaUsageResourceItem result = new SuicaUsageResourceItem(
				getMachineType(), getUsagetype(), getPaymentMethod(), getSituation(),
				getRecordedAt(), getReminder(), getSerial(), stationUrls);
		if (isTrainUsage()) {
			stationUrls.add(trainBaseUrl + getSrcTrainStation());
			stationUrls.add(trainBaseUrl + getDstTrainStation());
			return result;
		}
		if (isBusUsage()) {
			stationUrls.add(busBaseUrl + getBusStation());
			return result;
		}
		return result;
	}
	
	public short getMachineType() { return record[INDEX_MACHINE_TYPE]; }
	public short getUsagetype() { return record[INDEX_USAGE_TYPE]; }
	public short getPaymentMethod() { return record[INDEX_PAYMENT_METHOD]; }
	public short getSituation() { return record[INDEX_SITUATION]; }
	public long getRecordedAt() { 
		int date = (record[INDEX_RECORDED_AT] << 8) + record[INDEX_RECORDED_AT + 1];
		int year = ((date & DATE_YEAR_MASK) >> DATE_YEAR_SHIFT);
		int month = (date & DATE_MONTH_MASK) >> DATE_MONTH_SHIFT;
		int day = (date & DATE_DAY_MASK);
		calendar.clear();
		calendar.set(year + 2000, month - 1, day);
		return calendar.getTimeInMillis();
	}
	public int getReminder() { return record[INDEX_REMINDER] + (record[INDEX_REMINDER + 1] << 8); }
	public int getSerial() { return (record[INDEX_SERIAL] << 8) + record[INDEX_SERIAL + 1]; }
	
	public boolean isTrainUsage() {
		short target = (short)(getUsagetype() & 0x7f);
		for (short trainUsage : TRAIN_USAGE_TYPES)
			if (trainUsage == target)
				return true;
		return false;
	}
	public boolean isBusUsage() {
		short target = (short)(getUsagetype() & 0x7f);
		for (short busUsage : BUS_USAGE_TYPES)
			if (busUsage == target)
				return true;
		return false;
	}
	public boolean isShopUsage() {
		short target = (short)(getUsagetype() & 0x7f);
		for (short shopUsage : SHOP_USAGE_TYPES)
			if (shopUsage == target)
				return true;
		return false;
	}
	public int getSrcTrainStation() {
		return ((record[INDEX_REGION] & 0xC0) << 10) + (record[INDEX_STATION] << 8) + (record[INDEX_STATION + 1]);
	}
	public int getDstTrainStation() {
		return ((record[INDEX_REGION] & 0x30) << 12) + (record[INDEX_STATION + 2] << 8) + (record[INDEX_STATION + 3]);
	}
	public long getBusStation() {
		return (record[INDEX_STATION] << 24) + (record[INDEX_STATION + 1] << 16)
			+ (record[INDEX_STATION + 2] << 8) + record[INDEX_STATION + 3];
	}
	
	
	public static class SuicaUsageResouceItemBuilderException extends NFCBaseException {
		
	}
}
